package cn.iocoder.yudao.module.hs.service.material;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.hs.controller.admin.bigscreen.vo.WarehouseMaterialCount;
import cn.iocoder.yudao.module.hs.controller.admin.material.vo.MaterialModeRespVO;
import cn.iocoder.yudao.module.hs.controller.admin.material.vo.MaterialPageReqVO;
import cn.iocoder.yudao.module.hs.controller.admin.material.vo.MaterialSaveReqVO;
import cn.iocoder.yudao.module.hs.dal.dataobject.material.MaterialDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.materialbox.MaterialBoxDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.materialmode.MaterialModeDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.tray.TrayDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.warehouse.WarehouseDO;
import cn.iocoder.yudao.module.hs.dal.mysql.material.MaterialMapper;
import cn.iocoder.yudao.module.hs.dal.mysql.materialbox.MaterialBoxMapper;
import cn.iocoder.yudao.module.hs.enums.material.MaterialConstants;
import cn.iocoder.yudao.module.hs.enums.qcorder.QcResultEnum;
import cn.iocoder.yudao.module.hs.enums.warehouse.WarehouseStatusEnum;
import cn.iocoder.yudao.module.hs.enums.warehouse.WarehouseTypeEnum;
import cn.iocoder.yudao.module.hs.framework.core.utils.MaterialLogUtils;
import cn.iocoder.yudao.module.hs.service.entryorderdetail.EntryOrderDetailService;
import cn.iocoder.yudao.module.hs.service.materialmode.MaterialModeService;
import cn.iocoder.yudao.module.hs.service.shiporder.ShipOrderService;
import cn.iocoder.yudao.module.hs.service.tray.TrayService;
import cn.iocoder.yudao.module.hs.service.warehouse.WarehouseService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.hs.enums.ErrorCodeConstants.MATERIAL_NOT_EXISTS;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_NO_WAREHOUSE;

/**
 * 物料信息 Service 实现类
 *
 * @author 航盛
 */
@Service
@Validated
public class MaterialServiceImpl implements MaterialService {

    @Resource
    private MaterialMapper materialMapper;
    @Resource
    private AdminUserApi adminUserApi;
    @Lazy
    @Resource
    private ShipOrderService shipOrderService;
    @Resource
    private MaterialModeService materialModeService;
    @Resource
    private WarehouseService warehouseService;
    @Resource
    private MaterialBoxMapper materialBoxMapper;
    @Resource
    private TrayService trayService;
    @Lazy
    @Resource
    private EntryOrderDetailService entryOrderDetailService;

    @Override
    public Long createMaterial(MaterialSaveReqVO createReqVO) {
        // 插入
        MaterialDO material = BeanUtils.toBean(createReqVO, MaterialDO.class);
        materialMapper.insert(material);
        // 返回
        return material.getId();
    }

    @Override
    public void createMaterials(List<MaterialSaveReqVO> materialList) {
        List<MaterialDO> materialDOList = BeanUtils.toBean(materialList, MaterialDO.class);
        materialMapper.insertBatch(materialDOList);
    }

    @Override
    public void updateMaterial(MaterialSaveReqVO updateReqVO) {
        // 校验存在
        validateMaterialExists(updateReqVO.getId());
        // 更新
        MaterialDO updateObj = BeanUtils.toBean(updateReqVO, MaterialDO.class);
        materialMapper.updateById(updateObj);
    }

    @Override
    public void deleteMaterial(Long id) {
        // 校验存在
        validateMaterialExists(id);
        // 删除
        materialMapper.deleteById(id);
    }

    private void validateMaterialExists(Long id) {
        if (materialMapper.selectById(id) == null) {
            throw exception(MATERIAL_NOT_EXISTS);
        }
    }

    private MaterialDO validateMaterialExists(String code) {
        MaterialDO materialDO = getMaterial(code);
        if (ObjectUtil.isNull(materialDO)) {
            throw exception(MATERIAL_NOT_EXISTS);
        }
        return materialDO;
    }

    @Override
    public MaterialDO getMaterial(Long id) {
        return materialMapper.selectById(id);
    }

    @Override
    public PageResult<MaterialDO> getMaterialPage(MaterialPageReqVO pageReqVO) {
        return materialMapper.selectPage(pageReqVO);
    }

    @Override
    public PageResult<MaterialModeRespVO> getMaterialModePage(MaterialPageReqVO pageReqVO) {
        AdminUserRespDTO user = adminUserApi.getUser(getLoginUserId());
        if (user.getWarehouseId() == null) {
            throw exception(USER_NO_WAREHOUSE);
        }
        pageReqVO.setWarehouseId(user.getWarehouseId());
        Page<MaterialModeRespVO> page = new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());
        IPage<MaterialModeRespVO> materialModePage = materialMapper.getMaterialModePage(page, pageReqVO);
        for (MaterialModeRespVO record : materialModePage.getRecords()) {
            Integer notShipOverMaterialsCount = shipOrderService.getNotShipOverMaterialsCount(user.getWarehouseId(), record.getModeCode());
            record.setNotShipCount(notShipOverMaterialsCount);
            LambdaQueryWrapperX<MaterialDO> queryWrapper = new LambdaQueryWrapperX<>();
            queryWrapper.eq(MaterialDO::getWarehouseId, user.getWarehouseId())
                    .eq(MaterialDO::getModeCode, record.getModeCode())
                    .in(MaterialDO::getQcResult, CollectionUtil.toList(QcResultEnum.DEFECT.getType(), QcResultEnum.BACK.getType()));
            List<MaterialDO> materialDOList = materialMapper.selectList(queryWrapper);
            record.setDefectCount(materialDOList.size());
            MaterialModeDO materialMode = materialModeService.getMaterialMode(record.getModeCode());
            if (materialMode != null) {
                record.setPartNumber(materialMode.getPartNumber());
                record.setPartNumberShort(materialMode.getPartNumberShort());
            } else {
                record.setPartNumber("/");
                record.setPartNumberShort("/");
            }
        }
        PageResult<MaterialModeRespVO> pageResult = new PageResult<>();
        pageResult.setList(materialModePage.getRecords());
        pageResult.setTotal(materialModePage.getTotal());
        return pageResult;
    }

    @Override
    public void updateMaterialEntryByTrayCode(String trayCode, Long warehouseId, Long deptId, String warehouseType) {
        List<MaterialDO> list = materialMapper.selectListByTrayCode(trayCode);
        if (CollectionUtil.isNotEmpty(list)) {
            Set<Long> materialIds = CollectionUtils.convertSet(list, MaterialDO::getId);
            LambdaUpdateWrapper<MaterialDO> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.set(MaterialDO::getStatus, WarehouseStatusEnum.ENTRY.getStatus())
                    .set(MaterialDO::getWarehousingTime, LocalDateTime.now())
                    .set(WarehouseTypeEnum.PRODUCTION.getType().equals(warehouseType), MaterialDO::getFifoTime, LocalDateTime.now())
                    .set(MaterialDO::getWarehouseId, warehouseId)
                    .set(MaterialDO::getDeptId, deptId)
                    .set(MaterialDO::getShipTime, null)
                    .in(MaterialDO::getId, materialIds);
            materialMapper.update(updateWrapper);

            // 三方仓物料入库时，判断是否满箱，如果存在未满箱的箱，则更新物料为不满箱，不能拣货
            if (WarehouseTypeEnum.TRIPARTITE.getType().equals(warehouseType)) {
                for (MaterialDO materialDO : list) {
                    if (ObjUtil.isNull(materialDO.getTripartiteFifoTime())) {
                        materialMapper.update(new LambdaUpdateWrapper<MaterialDO>()
                                .set(MaterialDO::getTripartiteFifoTime, materialDO.getFifoTime())
                                .eq(MaterialDO::getId, materialDO.getId()));
                    }
                }
                // 查询对应的托盘编码是否有物料箱
                List<MaterialBoxDO> boxList = materialBoxMapper.selectListByTrayCode(trayCode);
                if (CollectionUtil.isNotEmpty(boxList)) {
                    boxList.forEach(materialBoxDO -> {
                        List<MaterialDO> materialListByBoxCode = getMaterialListByBoxCode(materialBoxDO.getCode());
                        if (CollUtil.isNotEmpty(materialListByBoxCode)) {
                            MaterialModeDO materialMode = materialModeService.getMaterialMode(materialListByBoxCode.get(0).getModeCode());
                            if (ObjUtil.isNotNull(materialMode) && materialMode.getBoxCapacityCount() > materialListByBoxCode.size()) {
                                materialMapper.update(new LambdaUpdateWrapper<MaterialDO>()
                                        .set(MaterialDO::getIsFull, MaterialConstants.IS_FULL_NO)
                                        .eq(MaterialDO::getBoxCode, materialBoxDO.getCode()));
                            }
                        }
                    });
                }
            }
            // 记录物料日志
            MaterialLogUtils.setMaterialInfo(new ArrayList<>(materialIds));
        }
    }

    @Override
    public void updateMaterialTray(List<MaterialDO> materialDOList, String trayCode) {
        if (CollectionUtil.isNotEmpty(materialDOList)) {
            for (MaterialDO materialDO : materialDOList) {
                materialMapper.update(new LambdaUpdateWrapper<MaterialDO>()
                        .set(MaterialDO::getTrayCode, trayCode)
                        .eq(MaterialDO::getId, materialDO.getId()));
            }
        }
    }

    @Override
    public void updateMaterialTray(List<MaterialDO> materialDOList, String trayCode, String boxCode) {
        if (CollectionUtil.isNotEmpty(materialDOList)) {
            for (MaterialDO materialDO : materialDOList) {
                materialMapper.update(new LambdaUpdateWrapper<MaterialDO>()
                        .set(MaterialDO::getTrayCode, trayCode)
                        .set(MaterialDO::getBoxCode, boxCode)
                        .eq(MaterialDO::getId, materialDO.getId()));
            }
        }
    }

    @Override
    public List<MaterialDO> getMaterials(Long warehouseId, String modeCode) {
        LambdaQueryWrapperX<MaterialDO> query = new LambdaQueryWrapperX<>();
        query.eq(MaterialDO::getModeCode, modeCode)
                .eq(MaterialDO::getStatus, WarehouseStatusEnum.ENTRY.getStatus())
                .eq(MaterialDO::getIsFull, MaterialConstants.IS_FULL_YES)
                .eq(MaterialDO::getWarehouseId, warehouseId)
                .isNotNull(MaterialDO::getTrayCode)
                .and(i -> i.eq(MaterialDO::getQcResult, QcResultEnum.GOOD.getType())
                        .or(i1 -> i1.isNull(MaterialDO::getQcResult)));
        WarehouseDO warehouse = warehouseService.getWarehouse(warehouseId);
        if (WarehouseTypeEnum.TRIPARTITE.getType().equals(warehouse.getWarehouseType())) {
            query.orderByAsc(MaterialDO::getTripartiteFifoTime);
        } else if (WarehouseTypeEnum.PRODUCTION.getType().equals(warehouse.getWarehouseType())) {
            query.orderByAsc(MaterialDO::getFifoTime);
        }
        query.orderByAsc(MaterialDO::getBoxCode);
        return materialMapper.selectList(query);
    }

    @Override
    public List<MaterialDO> getMaterialsByModeCode(Long warehouseId, String modeCode) {
        return materialMapper.getMaterialsByModeCode(warehouseId, modeCode);
    }

    @Override
    public MaterialDO getMaterial(String materialCode) {
        return materialMapper.selectOne(MaterialDO::getCode, materialCode);
    }

    @Override
    public List<MaterialDO> getMaterialListByTrayCode(String trayCode) {
        return materialMapper.selectList(MaterialDO::getTrayCode, trayCode);
    }

    @Override
    public List<MaterialDO> getMaterialListByBoxCode(String boxCode) {
        return materialMapper.selectList(MaterialDO::getBoxCode, boxCode);
    }

    @Override
    public List<MaterialDO> getMaterialListByBoxCode(List<String> boxCodeList) {
        return materialMapper.selectList(new LambdaQueryWrapperX<MaterialDO>().in(MaterialDO::getBoxCode, boxCodeList));
    }

    @Override
    public List<MaterialDO> getMaterialListByCode(List<String> codeList) {
        return materialMapper.selectList(new LambdaQueryWrapperX<MaterialDO>().in(MaterialDO::getCode, codeList));
    }

    @Override
    public void updateTrayCodeByCode(String trayCode, String code) {
        MaterialDO material = validateMaterialExists(code);
        LambdaUpdateWrapper<MaterialDO> updateWrapper = new LambdaUpdateWrapper<MaterialDO>()
                .set(MaterialDO::getTrayCode, trayCode)
                .eq(MaterialDO::getId, material.getId());
        TrayDO tray = trayService.getTray(trayCode);
        if (tray != null && WarehouseStatusEnum.ENTRY.getStatus().equals(tray.getStatus())) {
            updateWrapper.set(MaterialDO::getStatus, WarehouseStatusEnum.ENTRY.getStatus())
                            .set(MaterialDO::getIsFull, MaterialConstants.IS_FULL_YES)
                            .set(MaterialDO::getWarehouseId, tray.getWarehouseId())
                            .set(MaterialDO::getShipTime, null);
            if (ObjectUtil.isNull(material.getTripartiteFifoTime())) {
                updateWrapper.set(MaterialDO::getTripartiteFifoTime, material.getFifoTime());
            }
            entryOrderDetailService.insertEnterOrderDetail(tray, material);
        }
        materialMapper.update(updateWrapper);
    }

    @Override
    public void updateTrayCodeBoxCodeByCode(String trayCode, String boxCode, String code) {
        MaterialDO material = validateMaterialExists(code);
        material.setBoxCode(boxCode);
        material.setTrayCode(trayCode);
        materialMapper.updateById(material);
    }

    @Override
    public void updateNullTrayCodeByCode(String code) {
        MaterialDO material = validateMaterialExists(code);
        LambdaUpdateWrapper<MaterialDO> updateWrapper = new LambdaUpdateWrapper<MaterialDO>()
                .set(MaterialDO::getTrayCode, null)
                .eq(MaterialDO::getId, material.getId());
        materialMapper.update(updateWrapper);
    }

    @Override
    public void updateNullBoxCodeByCode(String code) {
        MaterialDO material = validateMaterialExists(code);
        LambdaUpdateWrapper<MaterialDO> updateWrapper = new LambdaUpdateWrapper<MaterialDO>()
                .set(MaterialDO::getBoxCode, null)
                .eq(MaterialDO::getId, material.getId());
        materialMapper.update(updateWrapper);
    }

    @Override
    public void updateMaterialQcResult(String trayCode, String boxCode, String qcResult, String materialCode) {
        LambdaUpdateWrapper<MaterialDO> updateWrapper = new LambdaUpdateWrapper<MaterialDO>()
                .set(MaterialDO::getQcResult, qcResult)
                .set(MaterialDO::getQcTime, LocalDateTime.now())
                .eq(MaterialDO::getCode, materialCode);
        if (QcResultEnum.GOOD.getType().equals(qcResult)) {
            updateWrapper.set(MaterialDO::getTrayCode, trayCode).set(MaterialDO::getBoxCode, boxCode);
        } else if (QcResultEnum.DEFECT.getType().equals(qcResult)) {
            updateWrapper.set(MaterialDO::getTrayCode, trayCode).set(MaterialDO::getBoxCode, null);
        } else if (QcResultEnum.BACK.getType().equals(qcResult)) {
            updateWrapper.set(MaterialDO::getTrayCode, null).set(MaterialDO::getBoxCode, null);
        }
        TrayDO tray = trayService.getTray(trayCode);
        if (WarehouseStatusEnum.ENTRY.getStatus().equals(tray.getStatus())) {
            updateWrapper.set(MaterialDO::getStatus, WarehouseStatusEnum.ENTRY.getStatus())
                            .set(MaterialDO::getWarehouseId, tray.getWarehouseId())
                            .set(MaterialDO::getWarehousingTime, LocalDateTime.now());
        }
        materialMapper.update(updateWrapper);
    }

    @Override
    public void updateMaterialOut(Set<String> materialCodes, Long toWarehouseDeptId) {
        LambdaUpdateWrapper<MaterialDO> updateWrapper = new LambdaUpdateWrapper<MaterialDO>()
                .set(MaterialDO::getStatus, WarehouseStatusEnum.SHIP.getStatus())
                .set(MaterialDO::getShipTime, LocalDateTime.now())
                .set(MaterialDO::getWarehouseId, null)
                .set(toWarehouseDeptId != null, MaterialDO::getDeptId, toWarehouseDeptId)
                .in(MaterialDO::getCode, materialCodes);
        materialMapper.update(updateWrapper);
    }

    @Override
    public void updateMaterialRollback(Set<String> materialCodes, Long warehouseId) {
        LambdaUpdateWrapper<MaterialDO> updateWrapper = new LambdaUpdateWrapper<MaterialDO>()
                .set(MaterialDO::getStatus, WarehouseStatusEnum.ENTRY.getStatus())
                .set(MaterialDO::getShipTime, null)
                .set(MaterialDO::getWarehouseId, warehouseId)
                .in(MaterialDO::getCode, materialCodes);
        materialMapper.update(updateWrapper);
    }

    @Override
    public List<WarehouseMaterialCount> getWarehouseMaterialCountList() {
        return materialMapper.getTripartiteMaterialChart();
    }

    @Override
    public Integer getProductWarehouseCount() {
        return materialMapper.getProductWarehouseCount();
    }
}
